package javango.contrib.hibernate;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;

import javango.contrib.hibernate.annotations.Filter;
import javango.contrib.hibernate.annotations.ModelChoiceFieldProperties;
import javango.db.ManagerException;
import javango.db.Managers;
import javango.db.QuerySet;
import javango.forms.fields.AbstractChoiceField;
import javango.forms.widgets.WidgetFactory;

import com.google.inject.Inject;

public class ModelChoiceField<T> extends AbstractChoiceField<T, String> {

	protected Class<? extends T> model;
	protected QuerySet<?> querySet;

	protected HibernateUtil hibernateUtil;
	protected Managers managers;

	@Inject
	public ModelChoiceField(WidgetFactory widgetFactory, HibernateUtil hibernateUtil, Managers managers) {
		super(widgetFactory);
		this.hibernateUtil = hibernateUtil;
		this.managers = managers;		
	}	

	@Override
	public T clean(String[] values, Map<String, String> errors) {
		if (values == null || values.length == 0) {
			return clean("", errors);
		}
		
		return clean(values[0], errors);
	}

	protected Object correctKeyType(String value) throws ManagerException {
		return new HibernateQuerySetHelper(hibernateUtil, getModel(), null).convertToPk(value);
	}
	
	@Override
	public T clean(String value, Map<String, String> errors) {
		try {
			if (StringUtils.isEmpty(value)) {
				if (isRequired()) {
					errors.put(getName(), "This field is required.");
					return null;
				}
				if (isAllowNull()) {
					return null;
				}
				return null;  // yea allow null does not really mean much for model choice fields.
			}
			Object key = correctKeyType(value);
			if (key != null && !errors.containsKey(this.getName())) {
				 managers.forClass(model);
				T v1 = managers.forClass(model).get((Serializable)key);
				if (v1 == null) {
					errors.put(getName(), UNKNOWN_CHOICE_ERROR);					
				}
				return v1;
			}
		} catch (ManagerException e) {
			errors.put(getName(), "Unable to get data: " + e);
			return null;
		}
		return null;
	}
	

	@Override
	public Map<T, String> getChoices() {
		if (super.getChoices() != null) {
			return super.getChoices();
		}

		ModelChoiceMap map = new ModelChoiceMap(managers, model);
		if (querySet != null) {
			map.setQuerySet(querySet);
		}

		setChoices(map);
		return map;
	}

	@Override
	public void handleAnnotation(Annotation annotation) {		
		super.handleAnnotation(annotation);
		if (annotation instanceof ModelChoiceFieldProperties) {
			ModelChoiceFieldProperties mcfp = (ModelChoiceFieldProperties)annotation; 
			this.setModel(mcfp.modelClass());
			try {
				QuerySet<?> qs = null;
			
				if (mcfp.filters().length > 0) {
					qs = managers.forClass(getModel()).all();
					for(Filter f : mcfp.filters()) {
						String[] value = f.value();
						if (value != null && value.length > 0) {
							if (value != null && value.length == 1) {
								qs = qs.filter(f.property(), value[0]);
							} else {
								qs = qs.filter(f.property(), value);
							}
						}
						if (!"".equals(f.otherProperty())) {
							qs = qs.filterByProperty(f.property(), f.otherProperty());
						}
						if (f.nullCheck()) {
							qs = qs.filter(f.property(), null);
						}
					}
				}
				if (mcfp.orderby().length > 0) {
					qs = qs != null ? qs : managers.forClass(getModel()).all();
						qs = qs.orderBy(mcfp.orderby());
				}
				
				if (qs != null) setQuerySet(qs);
			} catch (ManagerException e) {
				// ARGG WHAT TO DO...  think of the children
			}
		}
	}

	public ModelChoiceField<T> setChoices(Collection<T> collection) {
		
		Map<T, String> choices = new LinkedHashMap<T,String>();
		
		try {
//			String keyField = managers.forClass(model).getPkProperty();
		
			for (T choice : collection) {
				choices.put(choice, choice.toString());
			}
			
		} catch (Exception e) {
			// TODO
		}
		setChoices(choices);
		
		return this;
	}

	public Class getModel() {
		return model;
	}

	public ModelChoiceField setModel(Class model) {
		this.model = model;
		return this;
	}

	public QuerySet<?> getQuerySet() {
		return querySet;
	}

	public ModelChoiceField setQuerySet(QuerySet<?> querySet) {
		this.querySet = querySet;
		return this;
	}

	@Override
	public Object cleanInitialValue(Object value) {
		value = super.cleanInitialValue(value);
		
		try {
			String keyField = managers.forClass(model).getPkProperty();
			return BeanUtils.getProperty(value, keyField);
		} catch (Exception e) {
			// TODO
		}
		return null;
	}

	
}
